﻿namespace Microsoft.DevDiv.Wpf.Samples.DrawVisualSample
{
    using System;
    using System.Windows.Controls;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Threading;
    using Microsoft.DevDiv.Wpf.Samples;
    using System.Windows.Media.Imaging;

    class SlideTransitionDecorator : Decorator
    {
        public static readonly DependencyProperty IsVectorTransitionEnabledProperty = DependencyProperty.Register(
            "IsVectorTransitionEnabled",
            typeof(bool),
            typeof(SlideTransitionDecorator),
            new PropertyMetadata(true));

        public bool IsVectorTransitionEnabled
        {
            get { return (bool)GetValue(IsVectorTransitionEnabledProperty); }
            set { SetValue(IsVectorTransitionEnabledProperty, value); }
        }

        public override UIElement Child
        {
            get
            {
                return base.Child;
            }
            set
            {
                UIElement oldChild = this.Child;

                if (oldChild != null && this.IsArrangeValid)
                {
                    ScheduleTransition(oldChild);
                }

                base.Child = value;
            }
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            if (this.clone != null)
            {
                drawingContext.DrawDrawing(this.clone);
            }
        }

        private void ScheduleTransition(UIElement oldChild)
        {
            if (CloneElement(this.Child, out clone, out slideTransform))
            {
                if (animationHelper == null)
                {
                    animationHelper = new AnimationHelper(DoAnimationStep, CompleteAnimation);
                }

                animationHelper.Start(this.Dispatcher);
            }
        }

        private void DoAnimationStep(double time)
        {
            this.slideTransform.X = this.RenderSize.Width * time;
            this.InvalidateVisual();
        }

        private void CompleteAnimation()
        {
            this.slideTransform = null;
            this.clone = null;
            this.InvalidateVisual();
        }

        private bool CloneElement(UIElement element, out Drawing child, out TranslateTransform childTransform)
        {
            DrawingGroup drawing = null;

            if (element != null)
            {
                if (this.IsVectorTransitionEnabled)
                {
                    drawing = new DrawingGroup();

                    using (DrawingContext drawingDc = drawing.Open())
                    {
                        drawingDc.DrawVisual(element, false);
                    }

                    child = drawing;
                }
                else
                {
                    BitmapSource bitmap = BitmapHelper.CreateBitmapSnapshot(element);

                    drawing = new DrawingGroup()
                    {
                        Children = {
                            new ImageDrawing(bitmap, new Rect(new Size(bitmap.Width, bitmap.Height)))
                        }
                    };
                    child = drawing;
                }

                drawing.Transform = childTransform = new TranslateTransform();
                return true;
            }
            else
            {
                child = null;
                childTransform = null;
                return false;
            }
        }

        private Drawing clone;
        private TranslateTransform slideTransform;
        private AnimationHelper animationHelper;
    }

    class AnimationHelper
    {
        public TimeSpan Duration = TimeSpan.FromSeconds(0.6);

        public AnimationHelper(Action<double> doStep, Action doCompleted)
        {
            if (doStep == null)
            {
                throw new ArgumentNullException("doStep");
            }

            this.doStep = doStep;
            this.doCompleted = doCompleted;
        }

        public void Start(Dispatcher dispatcher)
        {
            this.startTime = DateTime.Now;

            if (this.timer == null)
            {
                this.timer = new DispatcherTimer(
                    TimeSpan.FromSeconds(1 / 24.0),
                    DispatcherPriority.SystemIdle,
                    this.HandleTimerTick,
                    dispatcher);
            }

            timer.Start();
        }

        private void HandleTimerTick(object sender, EventArgs e)
        {
            DispatcherTimer senderAsTimer = (DispatcherTimer)sender;

            long duration = this.Duration.Ticks;
            if (duration > 0)
            {
                double time = (DateTime.Now - this.startTime).Ticks / (double)duration;
                if (0.0 <= time && time < 1.0)
                {
                    doStep(time);
                    return;
                }
            }

            senderAsTimer.Stop();

            doCompleted();
        }

        private DateTime startTime;
        private DispatcherTimer timer;
        private readonly Action<double> doStep;
        private readonly Action doCompleted;
    }

    static class BitmapHelper
    {
        public static BitmapSource CreateBitmapSnapshot(UIElement element)
        {
            double dpiXScale = 1.0;
            double dpiYScale = 1.0;

            PresentationSource source = PresentationSource.FromVisual(element);
            if (source != null)
            {
                CompositionTarget compositionTarget = source.CompositionTarget;
                if (compositionTarget != null)
                {
                    dpiXScale = compositionTarget.TransformToDevice.M11;
                    dpiYScale = compositionTarget.TransformToDevice.M22;
                }
            }

            RenderTargetBitmap bitmap = CreateRenderTargetBitmap(element, (int)(dpiXScale * 96), (int)(dpiYScale * 96));
            RenderWithoutVisualOffset(bitmap, element);
            bitmap.Freeze();
            return bitmap;
        }

        public static RenderTargetBitmap CreateRenderTargetBitmap(Visual visual, int dpiX, int dpiY)
        {
            Rect bounds = VisualTreeHelper.GetDescendantBounds(visual);
            return CreateRenderTargetBitmap(bounds.Width, bounds.Height, dpiX, dpiY);
        }

        public static RenderTargetBitmap CreateRenderTargetBitmap(double width, double height, int dpiX, int dpiY)
        {
            return new RenderTargetBitmap(
                (int)(width * dpiX / 96.0),
                (int)(height * dpiY / 96.0),
                dpiX,
                dpiY,
                PixelFormats.Pbgra32);
        }

        public static void RenderWithoutVisualOffset(RenderTargetBitmap bitmap, Visual visual)
        {
            DrawingVisual drawingVisual = new DrawingVisual();
            using (DrawingContext drawingContext = drawingVisual.RenderOpen())
            {
                Size size = VisualTreeHelper.GetDescendantBounds(visual).Size;
                drawingContext.DrawRectangle(
                    new VisualBrush(visual),
                    null,
                    new Rect(size));
            }
            bitmap.Render(drawingVisual);
        }
    }
}
